package org.springframework.security.authentication.dao;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper;
import org.springframework.security.core.userdetails.*;
import org.springframework.security.core.userdetails.cache.NullUserCache;
import org.springframework.util.Assert;

/**
 * @author Dillon
 * @date 2024/7/1
 * @slogan 致敬大师 致敬未来的你
 * @desc 抽象的 daoUserDetailAuthentication 提供类
 * 主要处理 UsernamePasswordAuthenticationToken 用户密码认证token
 */
public abstract class AbstractUserDetailsAuthenticationProvider
		implements AuthenticationProvider, InitializingBean, MessageSourceAware {

	protected final Log logger = LogFactory.getLog(getClass());

	protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();

	private UserCache userCache = new NullUserCache();

	private boolean forcePrincipalAsString = false;

	protected boolean hideUserNotFoundExceptions = true;

	private UserDetailsChecker preAuthenticationChecks = new DefaultPreAuthenticationChecks();

	private UserDetailsChecker postAuthenticationChecks = new DefaultPostAuthenticationChecks();

	/**
	 * 授权映射器 认证成功后，对于权限进行自定义处理
	 * 比如给用户添加一些特定的权限的
	 */
	private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();

	/**
	 * 额外的身份认证检查 子类实现
	 *
	 * @param userDetails    查询到的userDetail 数据
	 * @param authentication 认证实现类
	 * @throws AuthenticationException 认证异常
	 */
	protected abstract void additionalAuthenticationChecks(UserDetails userDetails,
			UsernamePasswordAuthenticationToken authentication) throws AuthenticationException;

	/**
	 * 后验方法
	 *
	 * @throws Exception 执行异常
	 */
	@Override
	public final void afterPropertiesSet() throws Exception {
		Assert.notNull(this.userCache, "A user cache must be set");
		Assert.notNull(this.messages, "A message source must be set");
		doAfterPropertiesSet();
	}

	/**
	 * 认证核心方法，执行认证
	 *
	 * @param authentication 认证实现类
	 * @return 是否认证成功
	 * @throws AuthenticationException 认证异常
	 */
	@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		// 认证对象类型判断
		Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
				() -> this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports",
						"只支持 UsernamePasswordAuthenticationToken 认证"));
		// 获取登录用户名 Principal.name
		String username = determineUsername(authentication);

		boolean cacheWasUsed = true;
		// 尝试从缓存获取，如果不配置，默认不存在缓存
		UserDetails user = this.userCache.getUserFromCache(username);
		if (user == null) {
			cacheWasUsed = false;
			try {
				// 这里目前尝试从 userDetailService 中获取
				user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
			} catch (UsernameNotFoundException ex) {
				this.logger.debug("通过用户名 '" + username + "' 未加载到用户");
				// 是否隐藏异常 默认隐藏 返回 BadCredentialsException
				if (!this.hideUserNotFoundExceptions) {
					throw ex;
				}
				throw new BadCredentialsException(this.messages
						.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
			}
			Assert.notNull(user, "用户返回null， 违反了接口定义");
		}
		try {
			// 获取到用户后 执行前置校验
			this.preAuthenticationChecks.check(user);
			// 子类执行自定义校验
			additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
		} catch (AuthenticationException ex) {
			// 认证异常 如果不是从缓存中获取，直接抛异常
			if (!cacheWasUsed) {
				throw ex;
			}
			// 如果从缓存中获取 尝试再次从数据源获取并执行校验
			// 可能因为升级等因素 缓存中获取到 但校验失败
			cacheWasUsed = false;
			// 这里目前尝试从 userDetailService 中获取
			user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
			// 获取到用户后 执行前置校验
			this.preAuthenticationChecks.check(user);
			// 子类执行自定义校验
			additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
		}
		// 执行后置校验
		this.postAuthenticationChecks.check(user);
		// 如果不是从缓存中获取，填充缓存
		if (!cacheWasUsed) {
			this.userCache.putUserInCache(user);
		}
		// 凭证设置 如果强制使用字符串 那么这里使用用户名来设置凭证
		Object principalToReturn = user;
		if (this.forcePrincipalAsString) {
			principalToReturn = user.getUsername();
		}
		// 创建认证对象
		return createSuccessAuthentication(principalToReturn, authentication, user);
	}

	/**
	 * 获取用户名 用户名需要设置在凭证对象的name属性中
	 *
	 * @param authentication 认证对象
	 * @return 用户名
	 */
	private String determineUsername(Authentication authentication) {
		return (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();
	}

	/**
	 * 认证成功，创建认证对象  UsernamePasswordAuthenticationToken
	 *
	 * @param principal      认证凭证
	 * @param authentication 认证实现类
	 * @param user           查询到用户实现类
	 * @return 认证成功对象
	 */
	protected Authentication createSuccessAuthentication(Object principal, Authentication authentication,
			UserDetails user) {
		// 这里注意这个构造函数，里面标记了认证成功
		UsernamePasswordAuthenticationToken result = UsernamePasswordAuthenticationToken.authenticated(principal,
				authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
		// 设置认证详情
		result.setDetails(authentication.getDetails());
		this.logger.debug("认证成功");
		return result;
	}

	/**
	 * 后置处理器 子类实现
	 *
	 * @throws Exception 执行异常
	 */
	protected void doAfterPropertiesSet() throws Exception {
	}

	/**
	 * 获取认证缓存实现类
	 *
	 * @return 认证缓存实现类
	 */
	public UserCache getUserCache() {
		return this.userCache;
	}

	/**
	 * 获取是否强制转换认证结果为字符串
	 *
	 * @return 设置值
	 */
	public boolean isForcePrincipalAsString() {
		return this.forcePrincipalAsString;
	}

	/**
	 * 获取是否隐藏用户找不到异常配置
	 *
	 * @return 设置值
	 */
	public boolean isHideUserNotFoundExceptions() {
		return this.hideUserNotFoundExceptions;
	}

	/**
	 * 获取认证用户
	 *
	 * @param username       用户名
	 * @param authentication 认证实现类
	 * @return UserDetails实现类
	 * @throws AuthenticationException 认证异常
	 */
	protected abstract UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
			throws AuthenticationException;

	/**
	 * 设置是否强制将认证凭证转换未字符串 默认不强制
	 *
	 * @param forcePrincipalAsString 设置值
	 */
	public void setForcePrincipalAsString(boolean forcePrincipalAsString) {
		this.forcePrincipalAsString = forcePrincipalAsString;
	}

	/**
	 * 设置认证失败 是否隐藏用户找不到异常 默认隐藏
	 *
	 * @param hideUserNotFoundExceptions 设置值
	 */
	public void setHideUserNotFoundExceptions(boolean hideUserNotFoundExceptions) {
		this.hideUserNotFoundExceptions = hideUserNotFoundExceptions;
	}

	/**
	 * 设置国际化消息
	 *
	 * @param messageSource 国际化消息
	 */
	@Override
	public void setMessageSource(MessageSource messageSource) {
		this.messages = new MessageSourceAccessor(messageSource);
	}

	/**
	 * 设置认证缓存实现类
	 *
	 * @param userCache 认证缓存实现类
	 */
	public void setUserCache(UserCache userCache) {
		this.userCache = userCache;
	}

	/**
	 * 判断是否支持认证
	 * 只处理 UsernamePasswordAuthenticationToken 作为实现类的认证
	 *
	 * @param authentication 认证实现类
	 * @return 是否支持认证
	 */
	@Override
	public boolean supports(Class<?> authentication) {
		return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
	}

	/**
	 * 获取前置认证检查器
	 *
	 * @return 前置认证检查器
	 */
	protected UserDetailsChecker getPreAuthenticationChecks() {
		return this.preAuthenticationChecks;
	}

	/**
	 * 设置前置认证检查器
	 *
	 * @param preAuthenticationChecks 前置认证检查器
	 */
	public void setPreAuthenticationChecks(UserDetailsChecker preAuthenticationChecks) {
		this.preAuthenticationChecks = preAuthenticationChecks;
	}

	/**
	 * 获取后置认证处理器
	 *
	 * @return 后置认证处理器
	 */
	protected UserDetailsChecker getPostAuthenticationChecks() {
		return this.postAuthenticationChecks;
	}

	/**
	 * 设置认证处检查器
	 *
	 * @param postAuthenticationChecks 后置认证检查器
	 */
	public void setPostAuthenticationChecks(UserDetailsChecker postAuthenticationChecks) {
		this.postAuthenticationChecks = postAuthenticationChecks;
	}

	/**
	 * 设置授权映射器，自定义处理认证成功的权限信息
	 *
	 * @param authoritiesMapper 授权处理器
	 */
	public void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) {
		this.authoritiesMapper = authoritiesMapper;
	}

	/**
	 * userDetail 默认前置校验实现类
	 */
	private class DefaultPreAuthenticationChecks implements UserDetailsChecker {

		/**
		 * 执行校验
		 *
		 * @param user 待校验实现类
		 */
		@Override
		public void check(UserDetails user) {
			// 校验账号是否锁定
			if (!user.isAccountNonLocked()) {
				AbstractUserDetailsAuthenticationProvider.this.logger.debug("认证失败 账号已锁定");
				throw new LockedException(AbstractUserDetailsAuthenticationProvider.this.messages
						.getMessage("AbstractUserDetailsAuthenticationProvider.locked", "用户账号锁定"));
			}
			// 账号可用校验
			if (!user.isEnabled()) {
				AbstractUserDetailsAuthenticationProvider.this.logger.debug("认证失败 账号被禁用");
				throw new DisabledException(AbstractUserDetailsAuthenticationProvider.this.messages
						.getMessage("AbstractUserDetailsAuthenticationProvider.disabled", "账号被禁用"));
			}
			// 账号过期校验
			if (!user.isAccountNonExpired()) {
				AbstractUserDetailsAuthenticationProvider.this.logger.debug("认证失败，账号已过期");
				throw new AccountExpiredException(AbstractUserDetailsAuthenticationProvider.this.messages
						.getMessage("AbstractUserDetailsAuthenticationProvider.expired", "账号已过期"));
			}
		}
	}

	/**
	 * userDetail 默认后置校验器
	 */
	private class DefaultPostAuthenticationChecks implements UserDetailsChecker {

		/**
		 * 执行校验
		 *
		 * @param user 待校验实现类
		 */
		@Override
		public void check(UserDetails user) {
			// 账号凭证过期校验
			if (!user.isCredentialsNonExpired()) {
				AbstractUserDetailsAuthenticationProvider.this.logger.debug("认证失败 凭证已过期");
				throw new CredentialsExpiredException(AbstractUserDetailsAuthenticationProvider.this.messages
						.getMessage("AbstractUserDetailsAuthenticationProvider.credentialsExpired", "凭证已过期"));
			}
		}

	}

}
